import random
import HFSP_test.PMX_utils as pmx
import numpy as np

from HFSP_test.Schedule_utils import Schedule


class MGA:
    def __init__(self, crossover_prob=0.8, mutation_prob=0.1, pop_size=10, chrom_size=8, select_ratio=0.01,
                 generation=500):
        self._crossover_prob = crossover_prob
        self._mutation_prob = mutation_prob
        self._pop_size = pop_size
        self._chrom_size = chrom_size
        self._select_ratio = select_ratio
        self._generation = generation
        self._population = []

    # 原始获得最大适应度
    def get_max_fitness(self):
        max_fitness = -1
        for index, value in enumerate(self._population):
            each_fitness = self.cal_fitness(value)
            if each_fitness >= max_fitness:
                max_fitness = each_fitness
            else:
                pass
        return max_fitness

    # 获取调度的适应度
    def get_max_schedule_fitness(self):
        max_fitness = -1
        for index, value in enumerate(self._population):
            each_fitness = max_hour - self.cal_schedule_fitness(value)
            if each_fitness >= max_fitness:
                max_fitness = each_fitness
            else:
                pass
        return max_fitness

    # 族群初始化
    def init_population(self, row_num=10, col_num=8):
        # population = np.zeros([row_num, col_num], int)
        # for index in range(0, row_num):
        #     for _index in range(0, col_num):
        #         population[index, _index] = np.random.choice([0, 1])
        # self._population = population

        p = [0, 0, 0, 1, 1, 1, 2, 2, 2]
        population = []
        for index in range(0, row_num):
            new_p = p.copy()
            random.shuffle(new_p)
            population.append(new_p)
        population_np = np.asarray(population)
        self._population = population_np
        pass

    # 生产调度的初始化
    def init_schedule_population(self):
        job_num = len(job_list)
        stage_num = len(job_list[0]['job_stage'])
        p = []
        for j in range(0, job_num):
            for s in range(0, stage_num):
                p.append(j)
        population = []
        for index in range(0, self._pop_size):
            new_p = p.copy()
            random.shuffle(new_p)
            population.append(new_p)
        population_np = np.asarray(population)
        self._population = population_np
        pass

    # 原始计算适应度
    def cal_fitness(self, input):
        fitness_val = 1
        fitness_val = np.sum(input)
        return fitness_val

    # 计算机器调度的适应度
    def cal_schedule_fitness(self, work_sequence):
        each_schedule = Schedule(machine_list, job_list, work_cost_matrix, work_sequence)
        job_stage_list = each_schedule.code2JobStage(work_sequence)
        for each_job_stage in job_stage_list:
            each_job_id = each_job_stage[0]
            each_job_stage = each_job_stage[1]
            each_schedule.eachStageDistribution(each_job_id, each_job_stage)
        # each_schedule.drawGantt()
        fitness_val = each_schedule.getMaxTime()
        return fitness_val

    # 生成pop_size个新对象，并在生成的时候染色体进行交叉和随机产生变异
    def crossover_and_mutation(self):
        new_population_list = []
        # 前一个和后一个交叉变异
        for father in self._population:
            child = father
            # 产生子代时不是必然发生交叉，而是以一定的概率发生交叉
            if np.random.rand() < self._crossover_prob:
                # 随机找一个母亲
                mother = self._population[np.random.randint(self._pop_size)]
                child = self.cross(child, mother)

            else:
                pass
            child = self.mutation(child)
            new_population_list.append(child)

        new_population = np.asarray(new_population_list)
        # 重新填充
        self._population = new_population.copy()
        pass

    # 原始交叉,会生成两个孩子,目前返回第一个
    def cross(self, child, mother):
        child1 = child.copy()
        child2 = child.copy()
        cross_points = np.random.randint(low=1, high=self._chrom_size)
        child1[cross_points:] = mother[cross_points:]
        child2[:cross_points] = mother[:cross_points]
        return child1

    # 原始变异，这里选择的是二进制的变异
    def mutation(self, chromosome):
        if np.random.rand() < self._mutation_prob:
            # 随机产生一个实数，代表要变异基因的位置
            mutate_points = np.random.randint(0, self._chrom_size)
            # 将变异点位置的二进制反转
            chromosome[mutate_points] = chromosome[mutate_points] ^ 1
        return chromosome

    # 生成pop_size个新对象，并在生成的时候染色体进行交叉和随机产生变异
    def PMXcross_and_changeLocationMutation(self):
        new_population_list = []
        # 前一个和后一个交叉变异
        for father in self._population:
            child = father
            # 产生子代时不是必然发生交叉，而是以一定的概率发生交叉
            if np.random.rand() < self._crossover_prob:
                # 随机找一个母亲
                mother = self._population[np.random.randint(self._pop_size)]
                child = self.PMXcross(child, mother)

            else:
                pass
            child = self.changeLocationMutation(child)
            new_population_list.append(child)

        new_population = np.asarray(new_population_list)
        # 重新填充
        self._population = new_population.copy()
        pass

    # PMX交叉
    def PMXcross(self, child, mother):
        child1 = child.copy()
        child2 = mother.copy()

        child1_seq = pmx.code2JobStage(child1)
        child2_seq = pmx.code2JobStage(child2)
        model_dict_list, model_seq_list, other_seq_list = pmx.transformJobSeq2Unique(child1_seq, child2_seq)
        new_child1, new_child2 = pmx.two_points_cross(model_seq_list, other_seq_list)
        model_seq_transform_list, other_seq_transform_list = pmx.transformUnique2JobSeq2(model_dict_list, new_child1,
                                                                                         new_child2)
        # print(model_seq_transform_list, other_seq_transform_list)
        return model_seq_transform_list

    # 变异，这里选择的是交换变异的变异
    def changeLocationMutation(self, chromosome):
        new_chromosome = pmx.changeLocationMutation(chromosome)
        return new_chromosome

    # 评估和选择，使用轮盘赌，返回选择的在population中的个体
    def select(self):
        # 每个个体适应度
        every_fitness = []
        for i in range(self._pop_size):
            each_every_fitness_value = self.cal_fitness(self._population[i])
            every_fitness.append(each_every_fitness_value)
        # 所有的适应度
        total_fitness = sum(every_fitness)

        # 计算所有的染色体的概率
        # every_prob_list = []
        # for value in every_fitness:
        #     if (total_fitness != 0):
        #         each_prob = value / total_fitness
        #     else:
        #         each_prob = 0
        #     every_prob_list.append(each_prob)
        # every_prob_np = np.asarray(every_prob_list)

        # 轮盘赌计算
        ids = np.random.choice(np.arange(self._pop_size), size=self._pop_size, replace=True,
                               p=every_fitness / total_fitness)

        new_population = self._population[ids]
        self._population = new_population.copy()

    # 调度的选择
    def select_schedule(self):
        # 每个个体适应度
        every_fitness = []
        for i in self._population:
            work_sequence = i
            # 通过调度顺序获取适应度
            each_every_fitness_value = max_hour - self.cal_schedule_fitness(work_sequence)
            every_fitness.append(each_every_fitness_value)
        # 所有的适应度
        total_fitness = sum(every_fitness)
        # 转化为数组
        every_fitness_np = np.asarray(every_fitness)
        # 轮盘赌计算
        ids = np.random.choice(np.arange(self._pop_size), size=self._pop_size, replace=True,
                               p=every_fitness_np / total_fitness)

        new_population = self._population[ids]
        self._population = new_population.copy()

    # 调度的选择，精英模式
    def select_schedule_elite(self):
        # 获取前百分之十的个体
        best_ten_percent = []
        # 临时排序列表
        value_fitness_list = []
        for index, value in enumerate(self._population):
            work_sequence = value
            # 通过调度顺序获取适应度
            each_every_fitness_value = max_hour - self.cal_schedule_fitness(work_sequence)
            value_fitness_list.append(each_every_fitness_value)
        value_fitness_list_np = np.array(value_fitness_list)
        sort_idx = np.argsort(value_fitness_list_np)
        best_ten_percent = self._population[sort_idx][-int(self._pop_size / 10):]

        # 将这前百分之十复制到剩余的族群中
        new_population = []
        for idx in range(0, self._pop_size):
            select_id = random.randint(0, int(self._pop_size / 10) - 1)
            new_chrome = best_ten_percent[select_id]
            new_population.append(new_chrome)

        self._population = new_population.copy()

    # 选择最好的调度
    def select_best(self):
        max_fitness = 0
        best_work_sequence = []
        for i in self._population:
            work_sequence = i
            # 通过调度顺序获取适应度
            each_every_fitness_value = max_hour - self.cal_schedule_fitness(work_sequence)
            if each_every_fitness_value > max_fitness:
                max_fitness = each_every_fitness_value
                best_work_sequence = i.copy()
        return best_work_sequence

    # 选择最差的调度
    def select_worst(self):
        min_fitness = max_hour
        worst_work_sequence = []
        for i in self._population:
            work_sequence = i
            # 通过调度顺序获取适应度
            each_every_fitness_value = max_hour - self.cal_schedule_fitness(work_sequence)
            if each_every_fitness_value < min_fitness:
                min_fitness = each_every_fitness_value
                worst_work_sequence = i.copy()
        return worst_work_sequence

    # 开始执行进化过程
    def run(self):
        # 先初始化种群
        self.init_schedule_population()
        first_seq = self._population[random.randint(0, self._pop_size - 1)]
        # first_seq = self.select_worst()
        print('first_seq', first_seq)
        first_schedule = Schedule(machine_list, job_list, work_cost_matrix, first_seq)
        job_stage_list = first_schedule.code2JobStage(first_seq)
        for each_job_stage in job_stage_list:
            each_job_id = each_job_stage[0]
            each_job_stage = each_job_stage[1]
            first_schedule.eachStageDistribution(each_job_id, each_job_stage)
        print('first_time', first_schedule.getMaxTime())
        first_schedule.drawGantt()

        for each_gen in range(0, self._generation):
            if each_gen<=int(self._generation/2):
                # 选择（轮盘赌模式）
                self.select_schedule()
            else:
                # 精英模式
                self.select_schedule_elite()
            # 两个染色体交叉、变异，并重新填充population族群
            # self.crossover_and_mutation()
            # PMX的交叉变异
            self.PMXcross_and_changeLocationMutation()
            # 获取最大的值
            max_fitness = self.get_max_schedule_fitness()
            # 打印
            # print('each_gen' + str(each_gen) + ',' + str(max_fitness))
            print('each_gen' + str(each_gen))

        # 输出最后一代最好的那个
        best_seq = self.select_worst()
        print('best_seq', best_seq)
        each_schedule = Schedule(machine_list, job_list, work_cost_matrix, best_seq)
        job_stage_list = each_schedule.code2JobStage(best_seq)
        for each_job_stage in job_stage_list:
            each_job_id = each_job_stage[0]
            each_job_stage = each_job_stage[1]
            each_schedule.eachStageDistribution(each_job_id, each_job_stage)
        print('best_time', each_schedule.getMaxTime())
        each_schedule.drawGantt()

        pass


if __name__ == '__main__':
    # 由于需要求最小值，设定一个上限减去适应度
    max_hour = 100
    # 机器信息
    machine_list = [
        {'machine_id': 0, 'machine_name': '总装线1'},
        {'machine_id': 1, 'machine_name': '总装线2'},
        {'machine_id': 2, 'machine_name': '总装线3'},
        {'machine_id': 3, 'machine_name': '总装线4'},
    ]
    # 工件信息
    job_list = [
        {'job_id': 0, 'job_name': '1号车',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段生产'},
                       {'stage_id': 1, 'stage_name': '2阶段生产'},
                       {'stage_id': 2, 'stage_name': '3阶段生产'},
                       {'stage_id': 3, 'stage_name': '4阶段生产'},
                       {'stage_id': 4, 'stage_name': '5阶段生产'}]},
        {'job_id': 1, 'job_name': '2号车',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段生产'},
                       {'stage_id': 1, 'stage_name': '2阶段生产'},
                       {'stage_id': 2, 'stage_name': '3阶段生产'},
                       {'stage_id': 3, 'stage_name': '4阶段生产'},
                       {'stage_id': 4, 'stage_name': '5阶段生产'}]},
        {'job_id': 2, 'job_name': '3号车',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段生产'},
                       {'stage_id': 1, 'stage_name': '2阶段生产'},
                       {'stage_id': 2, 'stage_name': '3阶段生产'},
                       {'stage_id': 3, 'stage_name': '4阶段生产'},
                       {'stage_id': 4, 'stage_name': '5阶段生产'}]},
        {'job_id': 3, 'job_name': '4号车',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段生产'},
                       {'stage_id': 1, 'stage_name': '2阶段生产'},
                       {'stage_id': 2, 'stage_name': '3阶段生产'},
                       {'stage_id': 3, 'stage_name': '4阶段生产'},
                       {'stage_id': 4, 'stage_name': '5阶段生产'}]},
        {'job_id': 4, 'job_name': '5号车',
         'job_stage': [{'stage_id': 0, 'stage_name': '1阶段生产'},
                       {'stage_id': 1, 'stage_name': '2阶段生产'},
                       {'stage_id': 2, 'stage_name': '3阶段生产'},
                       {'stage_id': 3, 'stage_name': '4阶段生产'},
                       {'stage_id': 4, 'stage_name': '5阶段生产'}]},
    ]
    # 加工信息，用三维数组表示,第一层数组，每一行都是一个工件，第二层开始，
    # 每一行表示该行（机器）加工第几列(第几道stage)所需时间
    # 比如0,2,0表示工件1，机器3，工序1的加工时间，为第一行中第三行的第一列的值，为2
    work_cost_matrix = [
        [[4, 2, 3, 4, 2],
         [4, 4, 2, 2, 2],
         [4, 3, 1, 1, 2],
         [4, 3, 1, 1, 1]],
        [[3, 2, 3, 4, 2],
         [3, 4, 3, 3, 5],
         [3, 3, 2, 2, 4],
         [3, 3, 2, 2, 1]],
        [[3, 2, 3, 1, 1],
         [3, 2, 2, 2, 2],
         [3, 4, 1, 2, 1],
         [3, 2, 2, 2, 1]],
        [[4, 2, 3, 1, 1],
         [3, 2, 2, 2, 2],
         [2, 4, 1, 2, 1],
         [3, 2, 2, 2, 1]],
        [[2, 2, 3, 1, 1],
         [2, 2, 2, 2, 2],
         [2, 4, 1, 2, 1],
         [2, 2, 2, 2, 1]]
    ]

    mga = MGA(crossover_prob=0.8, mutation_prob=0.1, pop_size=100, chrom_size=9, select_ratio=0.1, generation=100)
    mga.run()

    pass
